#include "Thread.h"
#include "Exception.h"
#include "current_thread.h"
#include "log/Logger.h"
#include "wrap/prctl.h"
#include "utility/assertion.h"


#include <cstring>
#include <iostream>
#include <cstdio>

Thread::Thread(const TrivialFunc func, const std::string &name):
    _func{std::move(func)},
    _name{name.empty() ? "Thread" : name},
    _latch{std::make_unique<Latch>(1)},
    _thread{},
    _tid{0},
    _started{false},
    _joined{false} {
    _total++;
    LOG_DEBUG << _name << " constructed.\n";
}

Thread::~Thread() {
    if (_started && !_joined) {
        _thread->detach();
    }
    LOG_DEBUG << "thread " << _name << " destructed.\n";
}

void Thread::start() {
    assert(!_started);
    _started = true;
    try {
        _thread = std::make_unique<std::thread>([this](){
            _tid = CurrentThread::tid();
            _latch->count_down();
            const auto *const name = _name.c_str();
            CurrentThread::set_thread_name(std::strlen(name) != 0 ? name : "Thread");
            Wrap::prctl_(Wrap::PRE_SET_NAME_, CurrentThread::thread_name());

#ifdef NDEBUG
            try {
                _func();
            }
            catch (const Exception &e) {
                CurrentThread::set_thread_name("crashed-by-exception");
                ::fprintf(stderr, "Exception caught in Thread %s\n", name);
                ::fprintf(stderr, "what: %s\n", e.what());
                ::fprintf(stderr, "stack trace: %s\n", e.stack_trace());
                ::abort();
            }
            catch (const std::exception &e) {
                CurrentThread::set_thread_name("crashed-by-std::exception");
                ::fprintf(stderr, "stack trace: %s\n", CurrentThread::stack_trace(true).c_str());
                ::fprintf(stderr, "std::exception caught in Thread %s\n", name);
                ::fprintf(stderr, "what: %s\n", e.what());
                
                ::abort();
            }
            catch (...) {
                CurrentThread::set_thread_name("crashed");
                ::fprintf(stderr, "stack trace: %s\n", CurrentThread::stack_trace(true).c_str());
                ::fprintf(stderr, "unknow exception caught in Thread: %s\n", name);
                ::abort();
            }
            
#else
            _func();
#endif
            
            LOG_DEBUG << CurrentThread::thread_name() << " finished.\n";
            CurrentThread::set_thread_name("finished");
        });
    }
    catch(...) {
        _started = false;
        LOG_SYSERROR <<"fail to start thread" << '\n';
        throw;
    }
    _latch->wait();
    assert(_tid > 0);
}

void Thread::join() {
    assert(_started);
    if(_joined) {
        LOG_DEBUG << _name << " has alreayd started.\n";
        return;
    } else {
        LOG_DEBUG << _name << " going to be joined\n";
    }
    assert(!_joined);
    _joined = true;
    if (!_thread->joinable()) {
        return;
    }
    _thread->join();
}
